package com.xqls.redis.lock;

import com.xqls.redis.RedisManager;
import com.xqls.util.ContextUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;

import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by BingZhong on 2017/7/29.
 * 
 * 基于Redis实现的分布式锁
 */
public final class RedisLockHelper {

    private static Logger logger = LoggerFactory.getLogger(RedisLockHelper.class);

    /**
     * redis操作帮助类,可以是其他封装了redis操作的类
     */
    private Jedis getJedis(){ RedisManager redisManager=(RedisManager) ContextUtil.getBean(RedisManager.class);
        return redisManager.getJedisPool().getResource();
    }

    private void closeJedis(Jedis jedis){
        if(jedis==null) return;
        RedisManager redisManager=(RedisManager)ContextUtil.getBean(RedisManager.class);
        redisManager.getJedisPool().returnResource(jedis);
    }

    public static final long DEFAULT_TIMEOUT = 30 * 1000;

    public static final long DEFAULT_SLEEP_TIME = 100;

    private RedisLockHelper( ) {

    }

    public static RedisLockHelper getInstance( ) {
        return new RedisLockHelper( );
    }

    /**
     * 创建锁
     * @param mutex 互斥量
     * @param timeout 锁的超时时间
     * @param sleepTime 线程自旋尝试获取锁时的休眠时间
     * @param timeUnit 时间单位
     * @return
     */
    public RedisLock newLock(String mutex, long timeout, long sleepTime, TimeUnit timeUnit) {
        logger.info("创建分布式锁,互斥量为{}", mutex);
        return new RedisLock(mutex, timeout, sleepTime, timeUnit);
    }

    public RedisLock newLock(String mutex, long timeout, TimeUnit timeUnit) {
        return newLock(mutex, timeout, DEFAULT_SLEEP_TIME, timeUnit);
    }
    public RedisLock newLock(String mutex, long timeout ) {
        return newLock(mutex, timeout, DEFAULT_SLEEP_TIME, TimeUnit.MILLISECONDS);
    }

    public RedisLock newLock(String mutex) {
        return newLock(mutex, DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS);
    }

    public class RedisLock {
        /**
         * 用于创建redis健值对的键，相当于互斥量
         */
        private final String mutex;

        /**
         * 锁的过期时间
         */
        private volatile long lockExpiresTime = 0;

        /**
         * 锁的
         */
        private final long timeout;

        /**
         * 每次循环获取锁的休眠时间
         */
        private final long sleepTime;

        /**
         * 锁的线程持有者
         */
        private volatile Thread lockHolder = null;

        private final ReentrantLock threadLock = new ReentrantLock();

        public RedisLock(String mutex, long timeout, long sleepTime, TimeUnit timeUnit) {
            this.mutex = mutex;
            this.timeout = timeUnit.toMillis(timeout);
            this.sleepTime = timeUnit.toMillis(sleepTime);
        }

        /**
         * 加锁,将会一直尝试获取锁,直到超时
         */
        public boolean lock(long acquireTimeout, TimeUnit timeUnit) throws InterruptedException {
            acquireTimeout = timeUnit.toMillis(acquireTimeout);
            long acquireTime = acquireTimeout + System.currentTimeMillis();
            threadLock.tryLock(acquireTimeout, timeUnit);
            try {
                while (true) {
                    boolean hasLock = tryLock();
                    if (hasLock) {
                        //获取锁成功
                        return true;
                    } else if (acquireTime < System.currentTimeMillis()) {
                        break;
                    }
                    Thread.sleep(sleepTime);
                }
            } finally {
                if(threadLock.isHeldByCurrentThread()) {
                    threadLock.unlock();
                }
            }

            return false;
        }

        /**
         * 尝试获取锁,无论是否获取到锁都将直接返回而不会阻塞
         */
        public boolean tryLock() {
            long currentTime = System.currentTimeMillis();
            String expires = String.valueOf(timeout + currentTime);
            Jedis   jedis=null;
            try {
                jedis=getJedis();
            //尝试设置互斥量
            if (jedis.setnx(mutex, expires) > 0) {
                acquireSuccess(expires);
                return true;
            } else {
                String currentLockTime = jedis.get(mutex);
                //检查锁是否超时
                if (Objects.nonNull(currentLockTime) && Long.parseLong(currentLockTime) < currentTime) {
                    //获取旧的锁时间并设置互斥量
                    String oldLockTime = jedis.getSet(mutex, expires);
                    //判断获取的旧的锁时间是否过期，若旧值过期即获锁成功
                    if (Long.parseLong(oldLockTime) < currentTime) {
                        acquireSuccess(expires);
                        return true;
                    }
                }

                return false;
            }
            }finally {
                closeJedis(jedis);
            }
        }

        /**
         * 该锁是否被锁住
         */
        public boolean isLock() {
            Jedis   jedis=null;
            try {
                jedis=getJedis();
            String currentLockTime = jedis.get(mutex);
            //存在互斥量且锁还为过时即锁住
            return Objects.nonNull(currentLockTime) && Long.parseLong(currentLockTime) > System.currentTimeMillis();
            }finally {
                closeJedis(jedis);
            }
        }

        public String getMutex() {
            return mutex;
        }

        /**
         * 解锁
         */
        public boolean unlock() {
            Jedis   jedis=null;
            try {
                jedis=getJedis();
            //只有锁的持有线程才能解锁
            if (lockHolder == Thread.currentThread()) {
                //判断锁是否超时，没有超时才将互斥量删除
                if (lockExpiresTime > System.currentTimeMillis()) {
                    jedis.del(mutex);
                    logger.info("删除互斥量[{}]", mutex);
                }
                lockHolder = null;
                logger.info("释放[{}]锁成功", mutex);

                return true;
            }

            return false;
        }finally {
            closeJedis(jedis);
        }
        }

        private void acquireSuccess(String expires) {
            lockExpiresTime = Long.parseLong(expires);
            lockHolder = Thread.currentThread();
            logger.info("获取[{}]锁成功", mutex);
        }
    }
}
